package com.atguigu.gulimall.seckill.Service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.SeckillOrderTo;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.MemberRespVo;
import com.atguigu.gulimall.seckill.Service.SeckillService;
import com.atguigu.gulimall.seckill.feign.CouponFeignService;
import com.atguigu.gulimall.seckill.feign.ProductFeignService;
import com.atguigu.gulimall.seckill.interceptor.LoginUserInterceptor;
import com.atguigu.gulimall.seckill.to.SecKillSkuRedisTo;
import com.atguigu.gulimall.seckill.vo.SeckillSessionsWithSKus;
import com.atguigu.gulimall.seckill.vo.SeckillSkuVo;
import com.atguigu.gulimall.seckill.vo.SkuInfoVo;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    CouponFeignService couponFeignService;

    @Autowired
    StringRedisTemplate redisTemplate;
    
    @Override
    public void uploadSecKillsLastest3Days() {
         //1、重复上架无需处理
         //扫描需要参与秒杀的活动
        R session = couponFeignService.getLates3DaySession();

        if(session.getCode()==0){
            //上架商品
            List<SeckillSessionsWithSKus> sessionData = session.getData(new TypeReference<List<SeckillSessionsWithSKus>>() {});
            //缓存到redis
            //1、缓存活动信息
            saveSessionInfos(sessionData);

            //2、缓存活动的关联商品信息
            saveSessionSkuInfos(sessionData);

            // ;Hash -> seckill:sessions key: start_endtime val:[skuIds]


        }
    }

    @Override
    public List<SecKillSkuRedisTo> getCurrentSeckillSkus() {

        //"seckill:sessions:1675008000000_1675015200000"
        //获取当前的秒杀场次
        long time = new Date().getTime();
        Set<String> keys = redisTemplate.keys(SESSIONS_PREFIX + "*");   //keys，模糊检索
        for (String key : keys) {
            String replace = key.replace("seckill:sessions:", ""); //拿到key中的时间信息
            String[] s = replace.split("_");
            long start = Long.parseLong(s[0]);
            long end = Long.parseLong(s[1]);
            if(time>=start&&time<=end){
                //获取这个秒杀场的所有商品信息
                List<String> range = redisTemplate.opsForList().range(key, -100, 100);
                BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                List<String> list = hashOps.multiGet(range);
                if (list != null) {
                    List<SecKillSkuRedisTo> collect = list.stream().map(item -> {
                        SecKillSkuRedisTo redis = JSON.parseObject((String) item, SecKillSkuRedisTo.class);
                        //redis.setRandomCode(null);当前秒杀时间段开始了，需要随机码
                        return redis;
                    }).collect(Collectors.toList());
                    return collect;
                }
                //拿到当前场次的信息了，直接推出就好了
                break;
            }
        }
        return null;
    }

    @Override
    public SecKillSkuRedisTo getSkuSeckillInfo(Long skuId) {

        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        Set<String> keys = hashOps.keys();
        String regx = "\\d_"+skuId;

        if(keys!=null&&keys.size()>0){
            for (String key : keys) {
                if (Pattern.matches(regx,key)) {
                    String json = hashOps.get(key);
                    SecKillSkuRedisTo skuRedisTo = JSON.parseObject(json, SecKillSkuRedisTo.class);
                    //随机码
                    //查询当前时间
                    long current = new Date().getTime();
                    //是否参与秒杀
                    if(current>=skuRedisTo.getStartTime() && current<=skuRedisTo.getEndTime()){
                        //到时间了，给随机码
                    }else {
                        //还没到时间，隐藏随机码
                        skuRedisTo.setRandomCode(null);
                    }
                    //查询到了，就返回，一个商品一般不会在多个时间段秒杀
                    return skuRedisTo;
                }
            }
        }
        return null;
    }

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Override
    public String kill(String killId, String key, Integer num){

        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();

        //1、从redis中查询当前秒杀商品的信息,
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        String s = hashOps.get(killId);
        if(!StringUtils.isEmpty(s)){
            SecKillSkuRedisTo redis = JSON.parseObject(s, SecKillSkuRedisTo.class);
            Long startTime = redis.getStartTime();
            Long endTime = redis.getEndTime();
            long time = new Date().getTime();
            long ttl = endTime - startTime;
            //1、校验时间
            if(time >= startTime && time<= endTime ){

                String randomCode = redis.getRandomCode();
                String skuId = redis.getPromotionSessionId() + "_" + redis.getSkuId();
                //2、校验随机码
                if(randomCode.equals(key)&&skuId.equals(killId)){
                    //3、校验数量
                    if(num <= redis.getSeckillLimit()){
                        //4、校验是否买过，幂等性处理，只要秒杀成功就去占位
                        //将用户的购买信息存到redis中，（key:用户id+场次id+skuid,val 买的数量）
                        String redisKey = memberRespVo.getId() + "_" + skuId;
                        //自动过期
                        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
                        if (aBoolean){

                                //占位成功，从来没有买过,扣减信号量
                                RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                                //没拿到，就会在这等，一定要秒杀到
                                //semaphore.acquire();
                                //尝试获取信号量，等待100ms,拿到就好，没拿到就算了，不等了
                                //获取信号量不会出现库存超卖问题，拿到信号量才下单，不直接查库存，扣减库存，

                            Boolean b = semaphore.tryAcquire(num);

                            System.out.println("??????????"+b);
                                if(b){
                                    //秒杀成功快速下单
                                    //快速下单，发送MQ消息，10ms
                                    String timeId = IdWorker.getTimeId();
                                    SeckillOrderTo orderTo = new SeckillOrderTo();

                                    orderTo.setOrderSn(timeId);
                                    orderTo.setMemberId(memberRespVo.getId());
                                    orderTo.setNum(num);
                                    orderTo.setPromotionSessionId(redis.getPromotionSessionId());
                                    orderTo.setSeckillPrice(redis.getSeckillPrice());
                                    orderTo.setSkuId(redis.getSkuId());

                                    rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",orderTo);

                                    return timeId;
                                }

                                return null;

                        }else {
                            //买过了
                            return null;
                        }

                    }else {
                        return null;
                    }
                }else {
                    return null;
                }
            }
        }
        return null;
    }

    private final String SESSIONS_PREFIX = "seckill:sessions:";
    private final String SKUKILL_CACHE_PREFIX = "seckill:skus:";

    @Autowired
    RedissonClient redissonClient;

    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:"; //+商品随机码

    private void saveSessionInfos(List<SeckillSessionsWithSKus> sessions) {

        if (sessions != null) {
            sessions.stream().forEach(session->{

                //拿到milliseconds
                Long startTime = session.getStartTime().getTime();
                Long endTime = session.getEndTime().getTime();

                //拿到当前时间段的 seckill:sessions
                String key = SESSIONS_PREFIX + startTime + "_" + endTime;

                //缓存活动信息
                //将sessions按照 seckill:sessions+秒杀时间段+sku_id存到 redis 中
                //seckill:sessions+秒杀时间段+sku_ids
                //将场次信息保存起来
                //保存三天内所有场次的秒杀商品id
                //每一个场次是一个list,里面包括该时间段内秒杀的商品的skuId;
                //幂等性保证
                Boolean hasKey = redisTemplate.hasKey(key);
                if (!hasKey){
                    //拿到所有的sku_id
                    List<String> collect = session.getRelationSkus().stream().map(item -> item.getPromotionSessionId()+"_"+item.getSkuId().toString()).collect(Collectors.toList());
                    redisTemplate.opsForList().leftPushAll(key,collect);
                    //设置过期时间
                    //redisTemplate.expireAt(key,new Date(endTime));
                }
            });
        }


    }


    //保存秒杀商品的具体信息，是一个map(skuId,SecKillSkuRedisTo)
    private void saveSessionSkuInfos(List<SeckillSessionsWithSKus> sessions) {
        sessions.stream().forEach(session->{

            //将sku信息存起来，按照 名为 seckill:skus 的hasp(sku_id,skuInfo)
            //sku的信息包括秒杀信息和基本信息
            BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);

            session.getRelationSkus().stream().forEach(seckillSkuVo->{

                String token = UUID.randomUUID().toString().replace("-", "");

                //TODO 上架幂等性处理
                if(!ops.hasKey(seckillSkuVo.getPromotionSessionId().toString()+"_"+seckillSkuVo.getSkuId().toString())) {

                SecKillSkuRedisTo redisTo = new SecKillSkuRedisTo();
                //hasp(sku_id,skuInfo)

                //1、前面获取到了秒杀场次和秒杀场次信息及skuId，现在获取sku的基本数据
                R skuInfo = productFeignService.getSkuInfo(seckillSkuVo.getSkuId());

                if (skuInfo.getCode() == 0) {
                    SkuInfoVo info = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {});
                    redisTo.setSkuInfo(info);
                }

                //2、sku的秒杀信息
                BeanUtils.copyProperties(seckillSkuVo, redisTo);

                //3、sku的秒杀时间信息
                redisTo.setStartTime(session.getStartTime().getTime());
                redisTo.setEndTime(session.getEndTime().getTime());

                //随机吗？标示秒杀业务，没到时间不可以购买
                redisTo.setRandomCode(token);

                //TODO 信号量,商品可以秒杀的数量作为信号量，限流，只限制getSeckillCount个流量进来
                //信号量防止秒杀不成功就不访问数据库
                String jsonString = JSON.toJSONString(redisTo);
                ops.put(seckillSkuVo.getPromotionSessionId()+"_"+seckillSkuVo.getSkuId().toString(), jsonString);

                RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                semaphore.trySetPermits(seckillSkuVo.getSeckillCount());

                }
            });
        });
    }
}
